using System;
using System.Reflection;
using System.Collections;
using System.Text;
using FrameworkX.ObjectSpace.Data.Schema;
using FrameworkX.ObjectSpace.Data.Providers.SqlServer;
using FrameworkX.ObjectSpace.Data.Providers;

namespace FrameworkX.ObjectSpace.Util
{
	/// <summary>
	/// Helps to generate table creation and drop scripts
	/// </summary>
	internal class TableGenerator
	{
		private IDatabaseProvider _provider = null;
		public TableGenerator(IDatabaseProvider provider)
		{
			_provider = provider;
		}

		/// <summary>
		/// Drops the table
		/// </summary>
		/// <param name="objectType"></param>
		/// <returns></returns>
		public string GenerateTableDropScript (Type objectType)
		{
			StringBuilder sb = new StringBuilder();
			string tableName = objectType.Name;
			TableMapAttribute tableMap = CommonHelper.GetAttribute(objectType, 
				typeof(TableMapAttribute)) as TableMapAttribute;
			
			if (tableMap!=null)
			{
				tableName = tableMap.Name;
			}
			
			ISqlDataTypeTranslator translator = _provider.GetDataTypeTranslator();

			sb.Append(string.Format(" DROP TABLE {0}{1}{2}", translator.OpeningIdentifier, tableName, translator.ClosingIdentifier));
			return sb.ToString();
		}


		/// <summary>
		/// Generates table creation script for a database
		/// </summary>
		/// <param name="objectType"></param>
		/// <returns></returns>
		public string GenerateTableScript (Type objectType)
		{
			StringBuilder sb = new StringBuilder();

			StringBuilder sbAfterTable = new StringBuilder();
            StringBuilder sbBeforeTable = new StringBuilder();
            StringBuilder sbPostGeneration = new StringBuilder();

			string tableName = objectType.Name;
			
			TableMapAttribute tableMap = CommonHelper.GetAttribute(objectType, 
				typeof(TableMapAttribute)) as TableMapAttribute;
			
			if (tableMap!=null)
			{
				tableName = tableMap.Name;                
			}

			ISqlDataTypeTranslator translator = _provider.GetDataTypeTranslator();
			sb.Append(string.Format(" CREATE TABLE {0}{1}{2}", translator.OpeningIdentifier, tableName, translator.ClosingIdentifier));
			sb.Append (" ( \n ");
			
			PropertyInfo[] pinfos= objectType.GetProperties();
			foreach (PropertyInfo pinfo in pinfos)
			{
                StringBuilder sbField = new StringBuilder();

                ProcessPropertyForConstraint(pinfo, sbBeforeTable, sbField, sbAfterTable, sbPostGeneration); 
				string sql = GetCreateSQLForProperty(pinfo);
				if (!sql.Equals(string.Empty))
				{
                    sb.Append("\t");
					sb.Append(sql);
                    if (sbField.Length > 0)
                    {
                        sb.Append(" " + sbField.ToString() + " " );
                    }
					sb.Append(",\n");
				}
			}
			sb.Remove (sb.Length-2,1);

            ProcessTypeForConstraints(objectType, sbBeforeTable, null, sbAfterTable, sbPostGeneration);			

            StringBuilder sbFinal = new StringBuilder();
            if (sbBeforeTable.Length > 0)
            {
                sbFinal.Append(sbBeforeTable.ToString());
            }
            sbFinal.Append(sb.ToString());
            if (sbAfterTable.Length > 0)
            {
                sbFinal.Append(sbAfterTable.ToString());
            }
            sbFinal.Append(" ) \n");

            if (sbPostGeneration.Length > 0)
            {
                sbFinal.Append(sbPostGeneration.ToString());
            }

			return sbFinal.ToString();
		}

        public void ProcessTypeForConstraints(Type typ, StringBuilder sbBeforeTable, StringBuilder sbField, StringBuilder sbAfterTable, StringBuilder sbPostGeneration)
        {
            ProcessTypeForConstraint<UniqueFieldsConstraintAttribute>(typ, sbBeforeTable, sbField, sbAfterTable, sbPostGeneration);
            ProcessTypeForConstraint<IndexAttribute>(typ, sbBeforeTable, sbField, sbAfterTable, sbPostGeneration);
        }

        public bool ProcessTypeForConstraint<T>(Type typ, StringBuilder sbBeforeTable, StringBuilder sbField, StringBuilder sbAfterTable, StringBuilder sbPostGeneration)
            where T : ConstraintAttribute
        {
            T constraint = (T)Util.CommonHelper.GetAttribute(typ, typeof(T));
            if (constraint != null)
            {
                constraint.GenerateSql(_provider,typ, null, sbBeforeTable, sbField, sbAfterTable, sbPostGeneration);
                return true;
            }
            return false;
        }

        private void ProcessPropertyForConstraint(PropertyInfo pinfo, StringBuilder sbBeforeTable, StringBuilder sbField, StringBuilder sbAfterTable, StringBuilder sbPostGeneration)
        {
            ProcessPropertyForConstraint<PrimaryKeyAttribute>(pinfo, sbBeforeTable, sbField, sbAfterTable, sbPostGeneration);
            ProcessPropertyForConstraint<ForiegnKeyAttribute>(pinfo, sbBeforeTable, sbField, sbAfterTable, sbPostGeneration);    
        }


        /// <summary>
        /// Generate the constatn string 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="pinfo"></param>
        /// <param name="sbBeforeTable"></param>
        /// <param name="sbField"></param>
        /// <param name="sbAfterTable"></param>
        /// <returns></returns>
        private bool ProcessPropertyForConstraint<T>(PropertyInfo pinfo, StringBuilder sbBeforeTable, StringBuilder sbField, StringBuilder sbAfterTable, StringBuilder sbPostGeneration) where T : ConstraintAttribute
        {
            T constraint = (T)Util.CommonHelper.GetAttribute(pinfo, typeof(T));
            if (constraint != null)
            {
                constraint.GenerateSql(_provider,pinfo.DeclaringType, pinfo, sbBeforeTable, sbField, sbAfterTable, sbPostGeneration);
                return true;
            }
            return false;
        }
        
        private void GetExtraSqlForType(Type objectType, StringBuilder sb)
        {
            object[] attributes = objectType.GetCustomAttributes (typeof(UniqueFieldsConstraintAttribute), true);
            if (attributes.Length > 0)
            {
                UniqueFieldsConstraintAttribute attrib = attributes[0] as UniqueFieldsConstraintAttribute;
                string[] properties = attrib.Properties;
                sb.Append ("UNIQUE (");
                ISqlDataTypeTranslator translator = _provider.GetDataTypeTranslator();
                int i = 0;
                foreach (string propertyName in properties)
                {
                    PropertyInfo pinfo = objectType.GetProperty(propertyName);
                    string fieldName = propertyName;
                    if (i > 0)
                    {
                        sb.Append(",");
                    }
                    sb.Append(string.Format("{0}{1}{2}", translator.OpeningIdentifier, fieldName, translator.ClosingIdentifier));
                    i++;
                }
                sb.Append(")");
            }
        }


        /*
         * 
         *  CONSTRAINT [PK_Stock_1] PRIMARY KEY CLUSTERED 
(
	[Code] ASC
)
         * 
         * 
         * 
         */


		private string GetCreateSQLForProperty(PropertyInfo pinfo)
		{
			PropertyBoundAttribute pba = CommonHelper.GetAttribute (pinfo, 
				typeof(PropertyBoundAttribute)) as PropertyBoundAttribute;
			if (pba!=null)
			{
				if (!pba.IsBound)
				{
					return string.Empty;
				}
			}

			DataTypeAttribute dta = CommonHelper.GetAttribute(pinfo, 
					typeof (DataTypeAttribute)) as DataTypeAttribute;

			ISqlDataTypeTranslator translator = _provider.GetDataTypeTranslator();

			string columnName = string.Format("{0}{1}{2}", translator.OpeningIdentifier, pinfo.Name, translator.ClosingIdentifier);
			FieldMapAttribute fm = CommonHelper.GetAttribute (pinfo, 
				typeof(FieldMapAttribute)) as FieldMapAttribute;

			if (fm!=null)
			{
				columnName = string.Format("{0}{1}{2}", translator.OpeningIdentifier, fm.FieldName , translator.ClosingIdentifier);
			}

			if (dta==null)
			{
                dta = GetDefaultDataTypeProperty(pinfo);				
			}

			// return columnName + " " + dta.SqlTypeString;
			return columnName + " " + translator.TranslateDataTypeExpression(dta);

		}

		private DataTypeAttribute GetDefaultDataTypeProperty(PropertyInfo pinfo)
		{
			StringBuilder sb = new StringBuilder();
			if (!
					(
					pinfo.PropertyType.IsPrimitive 
					| pinfo.PropertyType.Equals(typeof(string))
					| pinfo.PropertyType.Equals(typeof(DateTime))
					| pinfo.PropertyType.Equals(typeof(System.Guid))
					)
				)
			{
				return new AnsiStringAttribute (2045);
			}
			else
			{
				switch(pinfo.PropertyType.FullName) 
				{
					case "System.String":
						return new AnsiStringAttribute(2045);
						break;
					case "System.Char" :
						return new AnsiStringAttribute(100);
						break;
					case "System.Int32" :
                        if (IsPrimary(pinfo))
                        {
                            return new Int32Attribute(true, false, true);
                        }
                        else
                        {
                            return new Int32Attribute();
                        }
						break;
                    case "System.Int64":
                        if (IsPrimary(pinfo))
                        {
                            return new Int64Attribute (false, true, 0);
                        }
                        else
                        {
                            return new Int64Attribute();
                        }
                        break;

					case "System.Decimal" :
						if (IsPrimary(pinfo))
						{
							return new DecimalAttribute(15, 0);
						}
						else
						{
							return new DecimalAttribute(15, 3);
						}
						break;
					case "System.Double" :
						throw new InvalidOperationException("Double value now allowed yet");
						break;
					case "System.DateTime" :
						return new DateTimeAttribute(true);
						break;
					case "System.int64" :
						return new Int16Attribute();
						break;
					case "System.Byte[]" :
						return new BinaryAttribute(1024, true);
						break;
					case "System.Guid" :
						return new GuidAttribute(true);
						break;
					case "System.Boolean":
						return new BooleanAttribute(true);
						break;
					default:
						throw new InvalidOperationException("Data type not supported");
				}

				/*
				if (IsPrimary(pinfo))
				{
					sb.Append(" IDENTITY");
				}
				*/
				
			}
		}

		private bool IsPrimary (PropertyInfo pinfo)
		{
			PrimaryKeyAttribute pk = CommonHelper.GetAttribute(pinfo,
				typeof(PrimaryKeyAttribute)) as PrimaryKeyAttribute;
			if (pk==null)
			{
				return false;
			}
			else
			{
				return true;
			}
		}

	}
}
